// /////////////////////////////////////////////////////////////////////////////
//
// Name:            SceneComponent.cpp
// Author:          Michael Bartsch (ZCCdark203)
//
// Desc :           Component wrapper of Irrlicht's ISceneNode.
//
// License:         Copyright (C) 2009 Michael Bartsch and Contributors
//
//                  This program is free software: you can redistribute it
//                  and/or modify it under the terms of the zlib/libpng License.
//                  See main.cpp for conditions of distribution and use.
//
// /////////////////////////////////////////////////////////////////////////////

// Include files
#include "SceneComponent.h"
#include "../../core/GameManager.h"


// SceneComponent class
// SceneComponent constructor (default).
SceneComponent::SceneComponent(Entity *parent)
: EntityComponent(parent)
{
    if(pParent != NULL)
    {
       // Common initialisation.
       init();

       // Get a pointer to the scene manager.
       ISceneManager *pScnMgr = GameManager::Instance()->getSceneManager();

       // Set up collision detection for this component.
       mTriSelector = pScnMgr->createTriangleSelectorFromBoundingBox(mSceneNode);
       mMetaSelector = pScnMgr->createMetaTriangleSelector();

       // Set misc. variables.
       mCanAffectParent = false;
       mEvokedParentChange = false;
       mLastPos = getPosition();
    }
}

// SceneComponent constructor (derivatives).
SceneComponent::SceneComponent(Entity *parent, bool isDerived)
: EntityComponent(parent)
{
    if(pParent != NULL)
      init();
}

// SceneComponent deconstructor.
SceneComponent::~SceneComponent()
{
    if(pParent != NULL)
    {
       if(mSceneNode != NULL)     mSceneNode->remove();
       if(mTriSelector != NULL)   mTriSelector->drop();
       if(mMetaSelector != NULL)  mMetaSelector->drop();
    }
}

// Common initialisation method.
void SceneComponent::init()
{
    // Set misc. variables.
    mName = "SceneComponent";

    // Register events.
    pParent->connectEventSignal("onPositionChange", this, &SceneComponent::onPositionChange);
    pParent->connectEventSignal("onRotationChange", this, &SceneComponent::onRotationChange);
    pParent->connectEventSignal("onUpdate", this, &SceneComponent::onUpdate);
    pParent->connectEventSignal("onPause", this, &SceneComponent::onPause);
    pParent->connectEventSignal("onUnPause", this, &SceneComponent::onUnPause);
}

// Returns a direct pointer to the ISceneNode.
ISceneNode* SceneComponent::getSceneNode()
{
    return mSceneNode;
}

// Returns a direct pointer to the meta selector of this component.
IMetaTriangleSelector* SceneComponent::getMetaSelector() const
{
    return mMetaSelector;
}

// Returns a direct pointer to the triangle selector of this component.
ITriangleSelector* SceneComponent::getTriangleSelector() const
{
    return mTriSelector;
}

// Adds an scene node animator to the component.
void SceneComponent::addAnimator(ISceneNodeAnimator *animator)
{
    mSceneNode->addAnimator(animator);
}

// Adds a collision response animator to the component.
void SceneComponent::addCollisionResponseAnimator(const vector3df &ellipsoidRadius,
                                                  const vector3df &gravityPerSecond,
                                                  const vector3df &ellipsoidTranslation,
                                                  f32 slidingValue)
{
    ISceneNodeAnimator *anim = GameManager::Instance()->getSceneManager()->
                                 createCollisionResponseAnimator(mMetaSelector, mSceneNode,
                                 ellipsoidRadius, gravityPerSecond, ellipsoidTranslation,
                                 slidingValue);

    mSceneNode->addAnimator(anim);
    anim->drop();
}

// Adds a fly circle animator to the component.
void SceneComponent::addFlyCircleAnimator(const vector3df &center, f32 radius, f32 speed,
                                          const vector3df &direction)
{
    ISceneNodeAnimator *anim = GameManager::Instance()->getSceneManager()->
                                 createFlyCircleAnimator(center, radius, speed, direction);

    mSceneNode->addAnimator(anim);
    anim->drop();
}

// Adds a fly straight animator to the component.
void SceneComponent::addFlyStraightAnimator(const vector3df &startPoint, const vector3df &endPoint,
                                            u32 timeForWay, bool loop)
{
    ISceneNodeAnimator *anim = GameManager::Instance()->getSceneManager()->
                                 createFlyStraightAnimator(startPoint, endPoint, timeForWay, loop);

    mSceneNode->addAnimator(anim);
    anim->drop();
}

// Searches for a SceneComponent within the given entity and if found adds the triangle selector of
// that component to the meta selector of this component.
void SceneComponent::addToMetaSelector(Entity *entity)
{
    if(entity != NULL && mMetaSelector != NULL)
    {
       SceneComponent *component = static_cast<SceneComponent*>(entity->
                                                                 getComponent("SceneComponent"));

       if(component != NULL)
       {
          ITriangleSelector *selector = component->getTriangleSelector();

          if(selector != NULL)
            mMetaSelector->addTriangleSelector(selector);
       }
    }
}

// Adds the triangle selector of the given component to the meta selector of this component.
void SceneComponent::addToMetaSelector(SceneComponent *component)
{
    if(component != NULL && mMetaSelector != NULL)
    {
        ITriangleSelector *selector = component->getTriangleSelector();

        if(selector != NULL)
          mMetaSelector->addTriangleSelector(selector);
    }
}

// Adds the given triangle selector to the meta selector of this component.
void SceneComponent::addToMetaSelector(ITriangleSelector *selector)
{
    if(selector != NULL && mMetaSelector != NULL)
      mMetaSelector->addTriangleSelector(selector);
}

// Gets the abolute position of the component.
vector3df SceneComponent::getAbsolutePosition() const
{
    return mSceneNode->getAbsolutePosition();
}

// Gets the automatic culling state.
E_CULLING_TYPE  SceneComponent::getAutomaticCulling() const
{
    return mSceneNode->getAutomaticCulling();
}

// Get the axis aligned, not transformed bounding box of this component.
const aabbox3df& SceneComponent::getBoundingBox() const
{
    return mSceneNode->getBoundingBox();
}

// Get amount of materials used by this component.
u32 SceneComponent::getMaterialCount() const
{
    return mSceneNode->getMaterialCount();
}

// Returns the relative transformation of the component.
matrix4 SceneComponent::getRelativeTransformation() const
{
    return mSceneNode->getRelativeTransformation();
}

// Gets the relative position of the component.
const vector3df SceneComponent::getPosition() const
{
    return mSceneNode->getAbsolutePosition() - pParent->getAbsolutePosition();
}

// Gets the rotation of the component.
const vector3df& SceneComponent::getRotation() const
{
    return mSceneNode->getRotation();
}

// Gets the scale of the component.
const vector3df& SceneComponent::getScale() const
{
    return mSceneNode->getScale();
}

// Removes all animators from this component.
void SceneComponent::removeAnimators()
{
    mSceneNode->removeAnimators();
}

// Sets the absolute position of the component.
void SceneComponent::setAbsolutePosition(const vector3df &position)
{
    mSceneNode->setPosition(position);
}

// Enables or disables automatic culling based on the bounding box.
void SceneComponent::setAutomaticCulling(E_CULLING_TYPE state)
{
    mSceneNode->setAutomaticCulling(state);
}

// Sets whether the component can affect the parent entity.
void SceneComponent::setCanAffectParent(bool value)
{
    mCanAffectParent = value;
}

// Sets if debug data like bounding boxes should be drawn.
void SceneComponent::setDebugDataVisible(s32 state)
{
    mSceneNode->setDebugDataVisible(state);
}

// Sets a material flag to a new value.
void SceneComponent::setMaterialFlag(E_MATERIAL_FLAG flag, bool value)
{
    mSceneNode->setMaterialFlag(flag, value);
}

// Loads and sets the texture of the specified layer in all materials of this component to the new
// texture.
void SceneComponent::setMaterialTexture(u32 layer, const std::string &fileName)
{
    // Set up variables.
    AssetGroup *assets = pParent->getAssetGroup();
    ITexture *texture = 0;

    // Retrieve pointer to the texture.
    if(assets != NULL)
    {
        // Unsubscribe from previous asset.
        std::map<u32, std::string>::iterator it = mTextureFileNames.find(layer);

        if(it != mTextureFileNames.end())
          assets->disconnectEventSignal(std::string("textures/") + it->second, this);

        // Get texture.
        TextureProcessor *processor = static_cast<TextureProcessor*>
                                      (assets->getAssetProcessor("textures"));
        texture = processor->getTexture(fileName);

        if(texture)
        {
           assets->connectEventSignal(std::string("textures/") + fileName, this,
                                        &SceneComponent::onTexture);
           mTextureFileNames[layer] = fileName;
        }
    }

    else
      texture = GameManager::Instance()->getDriver()->getTexture(fileName.c_str());

    // Set texture.
    mSceneNode->setMaterialTexture(layer, texture);
}

// Sets the texture of the specified layer in all materials of this component to the new texture.
void SceneComponent::setMaterialTexture(u32 layer, ITexture *texture)
{
    // Check if we need to unsubscribe from an asset group texture.
    AssetGroup *assets = pParent->getAssetGroup();

    if(assets != NULL)
    {
       std::map<u32, std::string>::iterator it = mTextureFileNames.find(layer);

       if(it != mTextureFileNames.end())
       {
          assets->disconnectEventSignal(std::string("textures/") + it->second, this);
          mTextureFileNames.erase(it);
       }
    }

    // Set texture.
    mSceneNode->setMaterialTexture(layer, texture);
}

// Sets the material type of all materials in this component to a new material type.
void SceneComponent::setMaterialType(E_MATERIAL_TYPE type)
{
    mSceneNode->setMaterialType(type);
}

// Sets the relative position of the component.
void SceneComponent::setPosition(const vector3df &position)
{
    mSceneNode->setPosition(pParent->getAbsolutePosition() + position);
}

// Sets the rotation of the component.
void SceneComponent::setRotation(const vector3df &rotation)
{
    mSceneNode->setRotation(pParent->getAbsoluteRotation() + rotation);
}

// Sets the relative scale of the component.
void SceneComponent::setScale(const vector3df &scale)
{
    mSceneNode->setScale(scale);
}

// Sets if the component should be visible or not.
void SceneComponent::setVisible(bool value)
{
    mSceneNode->setVisible(value);
}

// Events
// Updates the position of the component after its parent has been updated.
void SceneComponent::onPositionChange(void *p)
{
    if(!mEvokedParentChange)
    {
       vector3df diff = reinterpret_cast<vector3df*>(p)[0];
       mSceneNode->setPosition(mSceneNode->getPosition() + diff);
    }
}

// Updates the rotation of the component after its parent has been updated.
void SceneComponent::onRotationChange(void *p)
{
    if(!mEvokedParentChange)
    {
       vector3df diff = reinterpret_cast<vector3df*>(p)[0];
       mSceneNode->setRotation(mSceneNode->getRotation() + diff);
    }
}

// Updates the component if the parent is updated.
void SceneComponent::onUpdate(void *p)
{
    if(mCanAffectParent)
    {
       // We're going to check if an animator has changed the position of our scene node.
       // If that's the case we'll have to correct the change upstream.
       vector3df diff = mSceneNode->getPosition() - mLastPos;
       mLastPos = mSceneNode->getPosition();

       if(diff != vector3df(0))
       {
          mEvokedParentChange = true;
          pParent->setAbsolutePosition(pParent->getAbsolutePosition() + diff);
          mEvokedParentChange = false;
       }

       // We're going to check if an animator has changed the rotation of our scene node.
       // If that's the case we'll have to correct the change upstream.
       diff = mSceneNode->getRotation() - mLastRot;
       mLastRot = mSceneNode->getRotation();

       if(diff != vector3df(0))
       {
          mEvokedParentChange = true;
          pParent->setAbsoluteRotation(pParent->getAbsoluteRotation() + diff);
          mEvokedParentChange = false;
       }
    }
}

// Pauses the component if the parent is paused.
void SceneComponent::onPause(void *p)
{
    if(mSceneNode)
    {
       mWasVisible = mSceneNode->isVisible();
       mSceneNode->setVisible(false);
    }
}

// Unpauses the component if the parent is paused.
void SceneComponent::onUnPause(void *p)
{
    if(mSceneNode && mWasVisible)
      mSceneNode->setVisible(true);
}

// Responds to changes of a texture attached to this component.
void SceneComponent::onTexture(void *p)
{
    // Get texture data.
    ITexture *texture = reinterpret_cast<ITexture*>(p);

    // Determine layer.
    std::string fileName = (GameManager::Instance()->getDevice()->getFileSystem()->
                              getFileBasename(texture->getName())).c_str();

    u32 layer;

    std::map<u32, std::string>::iterator it;
    for(it = mTextureFileNames.begin(); it != mTextureFileNames.end(); it++)
    {
       if(it->second == fileName)
       {
           layer = it->first;
           break;
       }
    }

    if(it == mTextureFileNames.end())
      return;

    // Set texture.
    mSceneNode->setMaterialTexture(layer, texture);
}

// Serializes this object.
bool SceneComponent::onSerialization(SerializedAttributes *attributes)
{
    // Create a root.
    SerializedAttributes *root = new SerializedAttributes();

    // Serialize attributes.

    // Add root to the given collection of attributes.
    attributes->pushObject("SceneComponent", root);
    root->drop();

    // Return
    return true;
}

// Deserializes this object.
bool SceneComponent::onDeserialization(SerializedAttributes *attributes)
{
    // Retrieve root attribute from the collection.
    SerializedAttribute *rootAttribute = attributes->pullAttribute();

    if(rootAttribute == NULL || rootAttribute->getName() != "SceneComponent")
      return false;

    SerializedAttributes *root = reinterpret_cast<SerializedAttributes*>(rootAttribute->getObject());
    root->grab();

    rootAttribute->drop(); // We no longer need this attribute.

    // Process all attributes in the root collection.
    for(SerializedAttribute *attribute = root->pullAttribute(); attribute != NULL; attribute = root->pullAttribute())
    {
       // We no longer need a reference to the attribute.
       attribute->drop();
    }

    // We no longer need a reference to the root.
    root->drop();

    // We're done.
    return true;
}

// End of File
